use bitflags::bitflags;

use graphics::{Box2D, Frame, Point, Size};

use crate::contexts::{CxState, LifeCycleCtx};
use crate::event::{Event, LifeCycle};
use crate::widget::AnyWidget;
use crate::{
    contexts::{EventCtx, LayoutCtx, PaintCtx},
    widget::Widget,
};

pub struct Pod {
    pub widget: Box<dyn AnyWidget>,
    pub state: WidgetState,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PodFlags(u32);

impl Default for PodFlags {
    fn default() -> Self {
        PodFlags::INIT_FLAGS
    }
}

bitflags! {
    impl PodFlags: u32 {
        const ACTIVE = 0x200;
        const HAS_ACTIVE = 0x400;
        const HOT = 0x800;
        const REQUEST_UPDATE = ChangeFlags::UPDATE.bits() as _;
        const REQUEST_LAYOUT = ChangeFlags::LAYOUT.bits() as _;
        const REQUEST_PAINT = ChangeFlags::PAINT.bits() as _;
        const TREE_CHANGED = ChangeFlags::TREE.bits() as _;
        const VIEW_CONTEXT_CHANGED = ChangeFlags::VIEW_CONTEXT.bits() as _;

        const UPWARD_FLAGS = Self::REQUEST_UPDATE.bits()
            | Self::REQUEST_LAYOUT.bits()
            | Self::REQUEST_PAINT.bits()
            | Self::HAS_ACTIVE.bits()
            | Self::TREE_CHANGED.bits()
            | Self::VIEW_CONTEXT_CHANGED.bits();

        const INIT_FLAGS = Self::REQUEST_UPDATE.bits()
            | Self::REQUEST_LAYOUT.bits()
            | Self::REQUEST_PAINT.bits()
            | Self::TREE_CHANGED.bits()
            | Self::VIEW_CONTEXT_CHANGED.bits();
    }
}

#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
pub struct ChangeFlags(u8);

bitflags! {
    impl ChangeFlags: u8 {
        const UPDATE = 1;
        const LAYOUT = 2;
        const PAINT = 4;
        const TREE = 8;
        const VIEW_CONTEXT = 16;
    }
}

impl PodFlags {
    /// Flags to be propagated upwards.
    pub(crate) fn upwards(self) -> Self {
        self & PodFlags::UPWARD_FLAGS
    }
}

#[derive(Default, Debug)]
pub struct WidgetState {
    pub origin: Point,
    pub parent_origin: Point,
    pub size: Size,
    pub flags: PodFlags,
}

impl WidgetState {
    pub fn merge_up(&mut self, child_state: &WidgetState) {
        self.flags |= child_state.flags.upwards();
    }

    pub fn request(&mut self, flags: PodFlags) {
        self.flags |= flags
    }

    pub fn window_origin(&self) -> Point {
        self.parent_origin + self.origin.to_vector()
    }
}

impl Pod {
    pub fn new_from_box(widget: Box<dyn AnyWidget>) -> Self {
        Self {
            widget,
            state: Default::default(),
        }
    }

    pub fn mark(&mut self, flags: ChangeFlags) {
        self.state
            .request(PodFlags::from_bits_truncate(flags.bits() as _));
    }

    pub fn new<W: Widget + 'static>(widget: W) -> Self {
        Self::new_from_box(Box::new(widget))
    }

    pub fn event(&mut self, cx: &mut EventCtx<'_>, event: &Event) {
        if cx.is_handled {
            return;
        }
        let mut modified_event = None;
        let had_active = self.has_active();
        let recurse = match event {
            Event::MousePressed(e) => {
                self.set_hot_state(cx.cx_state, Some(e.pos));
                if had_active || self.is_hot() {
                    let mut mouse_info = e.clone();
                    mouse_info.pos -= self.state.origin.to_vector();
                    modified_event = Some(Event::MousePressed(mouse_info));
                    true
                } else {
                    false
                }
            }
            Event::MouseReleased(e) => {
                self.set_hot_state(cx.cx_state, Some(e.pos));
                if had_active || self.is_hot() {
                    let mut mouse_info = e.clone();
                    mouse_info.pos -= self.state.origin.to_vector();
                    modified_event = Some(Event::MouseReleased(mouse_info));
                    true
                } else {
                    false
                }
            }
            Event::MouseMove(e) => {
                let hot_changed = self.set_hot_state(cx.cx_state, Some(e.pos));
                if had_active || self.is_hot() || hot_changed {
                    let mut mouse_info = e.clone();
                    mouse_info.pos -= self.state.origin.to_vector();
                    modified_event = Some(Event::MouseMove(mouse_info));
                    true
                } else {
                    false
                }
            }
            Event::MouseWheel(e) => {
                self.set_hot_state(cx.cx_state, Some(e.pos));
                if had_active || self.is_hot() {
                    let mut mouse_info = e.clone();
                    mouse_info.pos -= self.state.origin.to_vector();
                    modified_event = Some(Event::MouseWheel(mouse_info));
                    true
                } else {
                    false
                }
            }
            Event::MouseLeft => {
                let hot_changed = self.set_hot_state(cx.cx_state, None);
                had_active || hot_changed
            }
            _ => self.is_hot() || self.has_active(),
        };
        if recurse {
            self.state.flags.set(PodFlags::HAS_ACTIVE, self.is_active());
            let mut inner_cx = EventCtx {
                cx_state: cx.cx_state,
                is_handled: false,
                widget_state: &mut self.state,
            };
            self.widget
                .event(&mut inner_cx, modified_event.as_ref().unwrap_or(event));
            cx.is_handled |= inner_cx.is_handled;
            cx.widget_state.merge_up(&self.state);
        }
    }

    pub fn paint(&mut self, cx: &mut PaintCtx, g: &mut Frame) {
        let origin = self.state.origin;
        let mut inner_cx = PaintCtx {
            cx_state: cx.cx_state,
            widget_state: &mut self.state,
            brush_cx: cx.brush_cx.clone(),
        };
        g.translate(origin.to_vector());
        self.widget.paint(&mut inner_cx, g);
        g.translate(-origin.to_vector());
        self.state.flags.remove(PodFlags::REQUEST_PAINT);
        cx.widget_state.merge_up(&self.state)
    }

    pub fn paint_raw(&mut self, cx: &mut PaintCtx, g: &mut Frame) {
        let mut inner_cx = PaintCtx {
            cx_state: cx.cx_state,
            widget_state: &mut self.state,
            brush_cx: cx.brush_cx.clone(),
        };
        self.widget.paint(&mut inner_cx, g);
    }

    pub fn layout(&mut self, cx: &mut LayoutCtx) -> Size {
        let mut child_cx = LayoutCtx {
            cx_state: cx.cx_state,
            widget_state: &mut self.state,
            brush_cx: cx.brush_cx.clone(),
        };
        let new_size = self.widget.layout(&mut child_cx);
        self.state.flags.remove(PodFlags::REQUEST_LAYOUT);
        cx.widget_state.merge_up(&self.state);
        self.state.size = new_size;
        self.state.size
    }

    pub fn lifecycle(&mut self, cx: &mut LifeCycleCtx, event: &LifeCycle) {
        let mut modified_event = None;
        let recurse = match event {
            LifeCycle::HotChanged(_) => false,
            LifeCycle::TreeUpdate => {
                if self.state.flags.contains(PodFlags::TREE_CHANGED) {
                    self.state.flags.remove(PodFlags::TREE_CHANGED);
                    true
                } else {
                    false
                }
            }
            LifeCycle::ViewContextChanged(view) => {
                self.state.parent_origin = view.window_origin;
                modified_event = Some(LifeCycle::ViewContextChanged(
                    view.translate_to(self.state.origin),
                ));
                self.state.flags.remove(PodFlags::VIEW_CONTEXT_CHANGED);
                true
            }
        };
        let mut child_cx = LifeCycleCtx {
            cx_state: cx.cx_state,
            widget_state: &mut self.state,
        };
        if recurse {
            let event = modified_event.as_ref().unwrap_or(event);
            self.widget.lifecycle(&mut child_cx, event);
            cx.widget_state.merge_up(&mut self.state);
        }
    }

    pub fn set_origin(&mut self, cx: &mut LayoutCtx, origin: Point) {
        if self.state.origin != origin {
            cx.request_view_context();
            self.state.flags.insert(PodFlags::VIEW_CONTEXT_CHANGED);
            cx.request_paint();
            self.state.origin = origin;
        }
    }

    /// Return true if hot state has changed
    pub fn set_hot_state(&mut self, cx: &mut CxState, mouse_pos: Option<Point>) -> bool {
        let region = Box2D::from_origin_and_size(self.state.origin, self.state.size);
        let was_hot = self.state.flags.contains(PodFlags::HOT);
        let is_hot = match mouse_pos {
            Some(pos) => region.contains(pos),
            None => false,
        };
        self.state.flags.set(PodFlags::HOT, is_hot);
        if is_hot != was_hot {
            let mut lifecycle_cx = LifeCycleCtx {
                cx_state: cx,
                widget_state: &mut self.state,
            };
            let event = LifeCycle::HotChanged(is_hot);
            self.widget.lifecycle(&mut lifecycle_cx, &event);
            true
        } else {
            false
        }
    }

    pub fn is_active(&self) -> bool {
        self.state.flags.contains(PodFlags::ACTIVE)
    }

    pub fn is_hot(&self) -> bool {
        self.state.flags.contains(PodFlags::HOT)
    }

    pub fn has_active(&self) -> bool {
        self.state.flags.contains(PodFlags::HAS_ACTIVE)
    }
}
